#region Copyright and Licensing Notice
// Copyright (C) 2011 Steven Bosnick
// 
// This program is open source software; you can redistribute it 
// and/or modifiy it under the terms of the Eclipse Public License,
// version 1.0.
// 
// This program is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A 
// PARTICULAR PURPOSE. See the Eclipse Public License for more
// details.
// 
// You should have received a copy of the Eclipse Public License 
// along with this program; if not, you can find a copy at
// "http://www.opensource.org/licenses/eclipse-1.0.php".
#endregion
using System;
using System.Threading.Tasks;
using Autofac.Features.OwnedInstances;
using BitwiseOre.SharedMemory.Core;
using BitwiseOre.SharedMemory.Core.Common;
using NUnit.Framework;
using Moq;

namespace BitwiseOre.Tests.SharedMemory.Connection
{
    [TestFixture]
    public sealed class ListenerFixture : AssertionHelper
    {
        [Test(Description = "Accepting a connection dequeues a guid from the listner queue.")]
        public void Accept_DequeuesFromListnerQueue()
        {
            var queue = new Mock<IListenerQueue>();
            queue.Setup(q => q.Dequeue(It.IsAny<TimeSpan>()))
                .Returns(Task<Guid>.Factory.StartNew(() => Guid.Empty));
            var listner = CreateListener(queue.Object);

            listner.Accept(TimeSpan.FromMinutes(1), g => new TestType()).Wait();

            queue.Verify(q => q.Dequeue(It.IsAny<TimeSpan>()),
                "Accepting a connection did not dequeue from the listner queue.");
        }

        [Test(Description = "Accepting a connection uses the supplied timeout when dequeueing")]
        public void Accept_DequeuesWithSuppliedTimeout()
        {
            Func<TimeSpan, ITimeoutHelper> factory =
                t =>
                    {
                        var helper = new Mock<ITimeoutHelper>();
                        helper.SetupGet(h => h.RemainingTime).Returns(t);
                        return helper.Object;
                    };
            var queue = new Mock<IListenerQueue>();
            queue.Setup(q => q.Dequeue(It.IsAny<TimeSpan>()))
                .Returns(Task<Guid>.Factory.StartNew(() => Guid.Empty));
            var listner = CreateListener(queue.Object, helperFactory:factory);
            var timeout = TimeSpan.FromMinutes(1);

            listner.Accept(timeout, g => new TestType());
            
            queue.Verify(q => q.Dequeue(It.Is<TimeSpan>(t => t == timeout)),
                "Accepting a connection did not use the supplied timeout when dequeueing.");
        }

        [Test(Description = "Accepting a connection uses the guid from the queue to invoke the supplied factory")]
        public void Accept_UsesDequeuedGuidToInvokeFactory()
        {
            var queue = new Mock<IListenerQueue>();
            var guid = Guid.NewGuid();
            queue.Setup(q => q.Dequeue(It.IsAny<TimeSpan>()))
                .Returns(Task<Guid>.Factory.StartNew(() => guid));
            var param = Guid.Empty;
            Func<string, Owned<IEventWaitHandle>> eventfactory = g => new Owned<IEventWaitHandle>(new Mock<IEventWaitHandle>().Object,
                                                                 new Mock<IDisposable>().Object);
            var lisener = CreateListener(queue.Object, eventfactory);

            lisener.Accept<TestType>(TimeSpan.Zero, g =>
                                                      {
                                                          param = g;
                                                          return new TestType();
                                                      }).Wait();

            Expect(param, EqualTo(guid), "Unexpected parameter passed to supplied factory, or factory not called.");
        }

        [Test(Description = "Accepting a connection creates an owned event after dequeueing a guid.")]
        public void Accept_CreatesOwnedEventUsingFactory()
        {
            var queue = new Mock<IListenerQueue>();
            var guid = Guid.NewGuid();
            queue.Setup(q => q.Dequeue(It.IsAny<TimeSpan>()))
                .Returns(Task<Guid>.Factory.StartNew(() => guid));
            var param = string.Empty;
            Func<string, Owned<IEventWaitHandle>> eventfactory = g =>
                                    {
                                        param = g;
                                        return new Owned<IEventWaitHandle>(new Mock<IEventWaitHandle>().Object,
                                                                           new Mock<IDisposable>().Object);
                                    };
            var listener = CreateListener(queue.Object, eventfactory);

            listener.Accept<TestType>(TimeSpan.Zero, g => new TestType()).Wait();

            Expect(param, EqualTo(guid.ToString()),
                "Event factory called with unexpected parameter or not called at all.");
        }

        [Test(Description = "Accepting a connection dequeues another guid if creating the owned event fails " +
                            "for the first one.")]
        public void Accept_DequeuesAgainIfUnableToCreateEvent()
        {
            var count = 0;
            var queue = new Mock<IListenerQueue>();
            var guid = Guid.NewGuid();
            queue.Setup(q => q.Dequeue(It.IsAny<TimeSpan>()))
                .Returns(Task<Guid>.Factory.StartNew(() => guid));
            Func<string, Owned<IEventWaitHandle>> eventfactory =
                g =>
                    {
                        count++;
                        if (count == 1)
                        {
                            return null;
                        }
                        return new Owned<IEventWaitHandle>(new Mock<IEventWaitHandle>().Object,
                                                           new Mock<IDisposable>().Object);
                    };
            var listener = CreateListener(queue.Object, eventfactory);

            listener.Accept<TestType>(TimeSpan.Zero, g => new TestType()).Wait();
            
            queue.Verify(q => q.Dequeue(It.IsAny<TimeSpan>()),Times.Exactly(2),
                         "Accepting a connection did not dequeue a second guid.");
        }


        [Test(Description = "Accepting a connection disposes the owned event after dequeueing a guid.")]
        public void Accept_DisposesOwnedEvent()
        {
            var queue = new Mock<IListenerQueue>();
            queue.Setup(q => q.Dequeue(It.IsAny<TimeSpan>()))
                .Returns(Task<Guid>.Factory.StartNew(Guid.NewGuid));
            var disposable = new Mock<IDisposable>();
            Func<string, Owned<IEventWaitHandle>> eventfactory = _ => new Owned<IEventWaitHandle>(new Mock<IEventWaitHandle>().Object,disposable.Object);
            var listener = CreateListener(queue.Object, eventfactory);

            listener.Accept<TestType>(TimeSpan.Zero, g => new TestType()).Wait();
            
            disposable.Verify(d => d.Dispose(),
                "Owned event not disposed after dequeueing a guid.");
        }

        [Test(Description = "Accepting a connection sets the owned event after dequeing a guid.")]
        public void Accept_SetsOwnedEvent()
        {
            var queue = new Mock<IListenerQueue>();
            queue.Setup(q => q.Dequeue(It.IsAny<TimeSpan>()))
                .Returns(Task<Guid>.Factory.StartNew(Guid.NewGuid));
            var eventhandle = new Mock<IEventWaitHandle>();
            Func<string, Owned<IEventWaitHandle>> eventfactory = _ => new Owned<IEventWaitHandle>(eventhandle.Object,new Mock<IDisposable>().Object);
            var listener = CreateListener(queue.Object, eventfactory);

            listener.Accept<TestType>(TimeSpan.Zero, g => new TestType()).Wait();
            
            eventhandle.Verify(e => e.Set(), "Event not set as expected.");
        }

        [Test(Description = "Waiting for a connection waits for the queue to be non-empty")]
        public void WaitFor_WaitsForNonEmptyListnerQueuue()
        {
            var queue = new Mock<IListenerQueue>();
            queue.Setup(q => q.WaitNotEmpty(It.IsAny<TimeSpan>()))
                .Returns(Task.Factory.StartNew(() => { }));

            var listenerQueue = queue.Object;
            var listener = CreateListener(listenerQueue);
            listener.WaitFor(TimeSpan.FromMinutes(1));

            queue.Verify(q => q.WaitNotEmpty(It.IsAny<TimeSpan>()));
        }

        private static Acceptor CreateListener(IListenerQueue listenerQueue,
                                               Func<string, Owned<IEventWaitHandle>> eventFactory = null,
                                               Func<TimeSpan, ITimeoutHelper> helperFactory = null)
        {
            helperFactory = helperFactory ?? (ts => new Mock<ITimeoutHelper>().Object);
            eventFactory = eventFactory ??
                           (n =>
                            new Owned<IEventWaitHandle>(new Mock<IEventWaitHandle>().Object,
                                                        new Mock<IDisposable>().Object));
            var factory = new Mock<IEventWaitHandleFactory>();
            factory.Setup(f => f.Create(It.IsAny<string>())).Returns(eventFactory);
            return new Acceptor(listenerQueue, factory.Object, helperFactory);
        }


        private class TestType : IDisposable
        {
            public void Dispose()
            {
            }
        }
    }
}